package cjyyp.nio网络编程;

import cjyyp.buffer.util.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

@Slf4j
public class nio利用selector处理阻塞问题 {
    public static void main(String[] args) throws IOException {
        Selector selector=Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        //channel注册到selector
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
        log.info("selectionKey：{}",selectionKey);
        //设置监听事件
        //注意：这里监听事件一共有4个
        /**
         * * connect - 客户端连接成功时触发
         * * accept - 服务器端成功接受连接时触发
         * * read - 数据可读入时触发，有因为接收能力弱，数据暂不能读入的情况
         * * write - 数据可写出时触发，有因为发送能力弱，数据暂不能写出的情况
         */
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
        while (true){
            log.info("selector触发事件之前。。。");
            selector.select();//当没有事件触发的时候会阻塞。注册的事件的触发才会执行。todo 事件发生后，如果不处理，就会变为非阻塞
            log.info("selector触发事件之后。。。");
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey next = iterator.next();
                iterator.remove();//todo 一定要删除，否则会有null异常
                log.info("key：{}",next);
                if (next.isAcceptable()){
                    ServerSocketChannel channel = (ServerSocketChannel) next.channel();
                    SocketChannel socketChannel = channel.accept();
                    socketChannel.configureBlocking(false);//设为不阻塞
                    // todo 每个 channel 都需要记录可能被切分的消息，因为 ByteBuffer 不能被多个 channel 共同使用，ByteBuffer线程不安全，因此需要为每个 channel 维护一个独立的 ByteBuffer
                    socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(3));//第三个参数就是给socketChannel增加一个附件ByteBuffer
                    log.info("连接已建立。。。");
                }else if (next.isReadable()){
                    try {
                        SocketChannel socketChannel = (SocketChannel) next.channel();
                        ByteBuffer byteBuffer = (ByteBuffer) next.attachment();//拿到这个key对应的附件ByteBuffer
                        int read = socketChannel.read(byteBuffer);
                        if (read==-1){//客户端正常断开返回-1
                            next.cancel();
                        }else {
                            byteBuffer.flip();
                            System.out.println(StandardCharsets.UTF_8.decode(byteBuffer).toString());
                            byteBuffer.clear();
                        }
                    }catch (Exception e){//客户端强制断开
                        e.printStackTrace();
                        next.cancel();//是从selector里面真正删除，不是删除set集合里面的
                    }
                }
            }
        }


    }
}
